<template>
	<div
		class="pf-VirtualScroll"
		:style="containerStyle()"
	>
		<div
			ref="viewRef"
			class="pf-VirtualScroll_view"
			:style="viewStyle()"
		>
			<slot
				v-for="obj in renderData"
				v-bind="obj"
			/>
		</div>
	</div>
</template>

<script setup lang="ts">
import type {
	FieldGetter,
	RelativePosition,
} from '../../../type';
import type {VirtualScrollItemScope,VirtualScrollInfo} from './type';
import {
	customRef,
	shallowRef,
	computed,
	watch,
	watchEffect,
	nextTick,
} from 'vue';
/**
 * fixme 插槽存在重复渲染问题
 * 虚拟滚动组件
 * @author 唐国雄
 * @date 2022/9/15
 * 属性----------------------
 * :data			列表数据
 * :data-map		映射数据
 * :direction		滚动方向
 * :min-size		最小尺寸
 * :preload			预加载数量
 * :view-size		视口大小
 * :scroll-offset	滚动距离
 * 插槽----------------------
 * #default			必须为组件且加key(否则可能有性能问题)
 * 方法----------------------
 * getInfo(index,pos?)	获取滚动距离和项目大小等信息
 */
if(typeof cache==='function'){
	cache=cache();
}
const {bucketSize,infos}=cache;

const props=withDefaults(defineProps<{
	data:Array<any>;
	dataMap?:FieldGetter<VirtualScrollItemScope,VirtualScrollItemScope>;
	direction?:Axis;
	minSize?:number;
	preload?:number;
	viewSize:number;
	scrollOffset:number;
}>(),{
	direction:'y',
	minSize:24,
	preload:3,
});
const viewRef=shallowRef<HTMLElement>(null);
const dataLen=shallowRef(0);
const scrollSize=shallowRef(0);
const offset=shallowRef(0);
const start=shallowRef(0);
const end=shallowRef(0);
//region 初始化实例缓存
const sizeCaches=customRef((track,trigger) => {
	const cache=[];
	const bucketCache=[];
	let defaultSize=0;
	let changed=false;
	let mustSetLength=false;
	const obj={
		//region 遍历器
		* loopBucket(){
			track();
			for(let i=0;i<bucketCache.length;++i){
				let bucket=bucketCache[i];
				if(bucket==null){
					bucket=defaultSize*bucketSize;
				}
				yield [i,bucket];
			}
		},
		* loop(start=0){
			track();
			while(start<cache.length){
				let size=cache[start];
				if(size==null){
					size=defaultSize;
				}
				yield [start,size];
				++start;
			}
		},
		//endregion
		//region 缓存实际大小
		setSize(index,size){
			let oldSize=cache[index];
			if(oldSize==null){
				oldSize=defaultSize;
			}
			if(oldSize!==size){
				const bucketIndex=Math.floor(index/bucketSize);
				let oldBucket=bucketCache[bucketIndex];
				if(oldBucket==null){
					oldBucket=defaultSize*bucketSize;
				}
				bucketCache[bucketIndex]=oldBucket+size-oldSize;
				changed=true;
				cache[index]=size;
			}
		},
		//endregion
		trigger(){
			if(changed){
				changed=false;
				trigger();
			}
		},
		//region 设置数据信息
		setDefaultSize(minSize){
			if(defaultSize!==minSize){
				defaultSize=minSize;
				cache.length=0;
				bucketCache.length=0;
				mustSetLength=true;
			}
		},
		setLength(len){
			if(mustSetLength||cache.length!==len){
				mustSetLength=false;
				cache.length=len;
				bucketCache.length=Math.floor(len/bucketSize);
				const lastBucketSize=len%bucketSize;
				if(lastBucketSize!==0){
					bucketCache.push(defaultSize*lastBucketSize);
				}
				trigger();
			}
		},
		//endregion
	};
	return {
		get(){
			return obj;
		},
		set(){},
	};
}).value;
watchEffect(() => {
	sizeCaches.setDefaultSize(props.minSize);
	const len=props.data.length;
	dataLen.value=len;
	sizeCaches.setLength(len);
});
//endregion
//region 样式
const containerStyle=(() => {
	return {
		[infos[props.direction].size]:scrollSize.value+'px',
	};
});
const viewStyle=(() => {
	const {transform,direction}=infos[props.direction];
	return {
		transform:`${transform}(${offset.value}px)`,
		flexDirection:direction,
	};
});
//endregion
//region 显示数据
const renderData=computed<VirtualScrollItemScope[]>(() => {
	const startVal=start.value;
	const {dataMap}=props;
	return props.data.slice(startVal,end.value).map((item,index) => {
		const scope={
			item,
			index:index+startVal,
		};
		if(dataMap){
			return dataMap(scope);
		}
		return scope;
	});
});
//endregion
//region 计算显示区域,偏移距离,滚动高度等
watchEffect(() => {
	let itemTop=0;
	let startIndex:number;
	let startOffset:number;
	let endIndex:number;
	const {preload,viewSize,scrollOffset}=props;
	if(!viewSize){
		startIndex=endIndex=0;
	}
	const len=dataLen.value;
	for(const [bucketIndex,bucket] of sizeCaches.loopBucket()){
		const curBucketBottom=itemTop+bucket;
		if(startIndex===undefined&&curBucketBottom>scrollOffset){
			for(const [itemIndex,size] of sizeCaches.loop(bucketIndex*bucketSize)){
				const curItemBottom=itemTop+size;
				if(startIndex===undefined&&curItemBottom>=scrollOffset){
					startIndex=itemIndex;
					startOffset=itemTop;
				}
				if(endIndex===undefined&&curItemBottom>scrollOffset+viewSize){
					endIndex=itemIndex+1+preload;
					if(endIndex>len){
						endIndex=len;
					}
					break;
				}
				itemTop=curItemBottom;
			}
		}
		itemTop=curBucketBottom;
	}
	if(startIndex===undefined){
		startIndex=len;
		startOffset=itemTop;
	}
	if(endIndex===undefined){
		endIndex=len;
	}
	scrollSize.value=itemTop;
	offset.value=startOffset;
	start.value=startIndex;
	end.value=endIndex;
});
//endregion
//region 缓存元素实际大小信息
watch(renderData,(() => {
	nextTick(() => {
		const data=renderData.value;
		const {children}=viewRef.value;
		const prop=infos[props.direction].getSize;
		for(let i=0;i<data.length;++i){
			const size=children[i][prop];
			const index=data[i].index;
			sizeCaches.setSize(index,size);
		}
		sizeCaches.trigger();
	});
}),{
	flush:'post',
});
//endregion
//region 导出的内容
defineExpose({
	getInfo(index:number,pos:RelativePosition='start'):VirtualScrollInfo{
		let offset=0;
		const max=dataLen.value-1;
		if(index<0){
			index+=max+1;
		}
		if(index>max){
			index=max;
		}
		const bucketIndex=Math.floor(index/bucketSize);
		for(const [i,bucket] of sizeCaches.loopBucket()){
			if(i>=bucketIndex){
				break;
			}
			offset+=bucket;
		}
		let itemSize;
		for(const [i,size] of sizeCaches.loop(bucketIndex*bucketSize)){
			itemSize=size;
			if(i>=index){
				break;
			}
			offset+=size;
		}
		if(pos!=='start'){
			let height=props.viewSize-itemSize;
			if(pos==='center'){
				if(height>0){
					offset-=Math.round(height/2);
				}
			}else{
				offset-=height;
			}
		}
		return {
			offset,
			itemSize,
		};
	},
});
//endregion
</script>

<script lang="ts">
import type {Axis} from '../../../type';
//region 类型声明
type Info={
	transform:'translateX'|'translateY';
	size:'width'|'height';
	getSize:'offsetWidth'|'offsetHeight';
	direction:'row'|'column';
};
interface Cache{
	bucketSize:number;
	infos:{
		// eslint-disable-next-line no-unused-vars
		[key in Axis]:Info
	};
}
//endregion
//region 全局缓存
let cache:Cache|(() => Cache)=function(){
	return {
		bucketSize:1000,
		infos:{
			x:{
				transform:'translateX',
				size:'width',
				getSize:'offsetWidth',
				direction:'row',
			},
			y:{
				transform:'translateY',
				size:'height',
				getSize:'offsetHeight',
				direction:'column',
			},
		},
	};
};
//endregion
export default {};
</script>